A given assembly may contain any number of distinct types. In the world of .NET, type is simply a general term used to refer to a member from the set {class, interface, structure, enumeration, delegate}. When you build solutions using a .NET-aware language, you will most likely interact with many of these types. For example, your assembly may define a single class that implements some number of interfaces. Perhaps one of the interface methods takes an enumeration type as an input parameter and returns a structure to the caller.
Recall that the CTS is a formal specification that documents how types must be defined in order to be hosted by the CLR. Typically, the only individuals who are deeply concerned with the inner workings of the CTS are those building tools and/or compilers that target the .NET platform. It is important, however, for all .NET programmers to learn about how to work with the five types defined by the CTS in their language of choice. Here is a brief overview.
Every .NET-aware language supports, at the very least, the notion of a class type, which is the cornerstone of OOP. A class may be composed of any number of members (such as constructors, properties, methods, and events) and data points (fields). In C#, classes are declared using the class keyword.
// A C# class type with 1 method. class Calc { public int Add(int x, int y) { return x + y; } }
Chapter 5 will begin your examination of building CTS class types with C#; however, Table 1-1 documents a number of characteristics pertaining to class types.
Table 1-1. CTS Class Characteristics
Class Characteristic | Meaning in Life |
---|---|
Is the class sealed or not? | Sealed classes cannot function as a base class to other classes. |
Does the class implement any interfaces? | An interface is a collection of abstract members that provide a contract between the object and object user. The CTS allows a class to implement any number of interfaces. |
Is the class abstract or concrete? | Abstract classes cannot be directly instantiated, but are intended to define common behaviors for derived types. Concrete classes can be instantiated directly. |
What is the visibility of this class? | Each class must be configured with a visibility keyword such as public or internal. Basically, this controls if the class may be used by external assemblies or only from within the defining assembly. |
Interfaces are nothing more than a named collection of abstract member definitions, which may be supported (i.e., implemented) by a given class or structure. In C#, interface types are defined using the interface keyword. By convention, all .NET interfaces begin with a capital letter I, as in the following example:
// A C# interface type is usually // declared as public, to allow types in other // assemblies to implement their behavior. public interface IDraw { void Draw(); }
On their own, interfaces are of little use. However, when a class or structure implements a given interface in its unique way, you are able to request access to the supplied functionality using an interface reference in a polymorphic manner. Interface-based programming will be fully explored in Chapter 9.
The concept of a structure is also formalized under the CTS. If you have a C background, you should be pleased to know that these user-defined types (UDTs) have survived in the world of .NET (although they behave a bit differently under the hood). Simply put, a structure can be thought of as a lightweight class type having value-based semantics. For more details on the subtleties of structures, see Chapter 4. Typically, structures are best suited for modeling geometric and mathematical data and are created in C# using the struct keyword.
// A C# structure type. struct Point { // Structures can contain fields. public int xPos, yPos; // Structures can contain parameterized constructors. public Point(int x, int y) { xPos = x; yPos = y;} // Structures may define methods. public void PrintPosition() { Console.WriteLine("({0}, {1})", xPos, yPos); } }
Enumerations are a handy programming construct that allow you to group name/value pairs. For example, assume you are creating a video game application that allows the player to select one of three character categories (Wizard, Fighter, or Thief). Rather than keeping track of simple numerical values to represent each possibility, you could build a custom enumeration using the enum keyword.
// A C# enumeration type. enum CharacterType { Wizard = 100, Fighter = 200, Thief = 300 }
By default, the storage used to hold each item is a 32-bit integer; however, it is possible to alter this storage slot if need be (e.g., when programming for a low-memory device such as a Windows mobile device). Also, the CTS demands that enumerated types derive from a common base class, System.Enum. As you will see in Chapter 4, this base class defines a number of interesting members that allow you to extract, manipulate, and transform the underlying name/value pairs programmatically.
Delegates are the .NET equivalent of a type-safe, C-style function pointer. The key difference is that a .NET delegate is a class that derives from System.MulticastDelegate, rather than a simple pointer to a raw memory address. In C#, delegates are declared using the delegate keyword.
// This C# delegate type can "point to" any method // returning an int and taking two ints as input. delegate int BinaryOp(int x, int y);
Delegates are useful when you wish to provide a way for one entity to forward a call to another entity and provide the foundation for the .NET event architecture. As you will see in Chapters 11 and 19, delegates have intrinsic support for multicasting (i.e., forwarding a request to multiple recipients) and asynchronous method invocations (i.e., invoking the method on a secondary thread).
Now that you have previewed each of the types formalized by the CTS, realize that most types take any number of members. Formally speaking, a type member is constrained by the set {constructor, finalizer, static constructor, nested type, operator, method, property, indexer, field, read-only field, constant, event}.
The CTS defines various adornments that may be associated with a given member. For example, each member has a given visibility trait (e.g., public, private, protected). Some members may be declared as abstract (to enforce a polymorphic behavior on derived types) as well as virtual (to define a canned, but overridable, implementation). Also, most members may be configured as static (bound at the class level) or instance (bound at the object level). The creation of type members is examined over the course of the next several chapters.
Note As described in Chapter 10, the C# language also supports the creation of generic types and generic members.
The final aspect of the CTS to be aware of for the time being is that it establishes a well-defined set of fundamental data types. Although a given language typically has a unique keyword used to declare an intrinsic CTS data type, all language keywords ultimately resolve to the same type defined in an assembly named mscorlib.dll. Consider Table 1-2, which documents how key CTS data types are expressed in various .NET languages.
Table 1-2. The Intrinsic CTS Data Types
CTS Data Type | VB .NET Keyword | C# Keyword | C++/CLI Keyword |
---|---|---|---|
System.Byte | Byte | byte | unsigned char |
System.SByte | SByte | sbyte | signed char |
System.Int16 | Short | short | short |
System.Int32 | Integer | int | int or long |
System.Int64 | Long | long | __int64 |
System.UInt16 | UShort | ushort | unsigned short |
System.UInt32 | UInteger | uint | unsigned int or unsigned long |
System.UInt64 | ULong | ulong | unsigned __int64 |
System.Single | Single | float | float |
System.Double | Double | double | double |
System.Object | Object | object | object^ |
System.Char | Char | char | wchar_t |
System.String | String | string | String^ |
System.Decimal | Decimal | decimal | Decimal |
System.Boolean | Boolean | bool | bool |
Given the fact that the unique keywords of a managed language are simply shorthand notations for a real type in the System namespace, we no longer have to worry about overflow/underflow conditions for numerical data, or how strings and Booleans are internally represented across different languages. Consider the following code snippets, which define 32-bit numerical variables in C# and Visual Basic, using language keywords as well as the formal CTS data type:
// Define some "ints" in C#. int i = 0; System.Int32 j = 0; ' Define some "ints" in VB. Dim i As Integer = 0 Dim j As System.Int32 = 0